#include "League.h"
#include "PeopleManager.h"
#include "Utilities.h"
#include "ServiceProvider.h"
#include "TeamManager.h"
#include "LeagueManager.h"
#include "RandomSystem.h"
#include "ZStream.h"
#include "MatchManager.h"
#include "MatchInterface.h"
#include "Types.h"

#include <cassert>

League::League(ServiceProvider* i_sp)
: m_sp(i_sp)
, m_calendar(i_sp)
, m_classification(i_sp)
, m_id(Utilities::INVALID_ID)
{
	assert(i_sp);
}

League::~League()
{
	Clear();
}

void League::Clear()
{
	m_teams.clear();
	m_calendar.Clear();
	m_classification.Clear();
	m_sortedTeams.clear();
}

void League::Initialize(STL::string& name, LeagueSettings &i_settings)
{
	m_leagueName = name.substr(0,MAX_LEAGUE_NAME_SIZE-1);
	m_leagueName += STL::string(MAX_LEAGUE_NAME_SIZE - m_leagueName.size(), ' ');
	m_settings = i_settings;
	m_teams.reserve(i_settings.numOfTeams);

	for (size_t i = 0; i < i_settings.numOfTeams; ++i)
	{
		Team* team = m_sp->GetTeamMgr().CreateNewTeam();
		size_t budget= m_settings.minStartingTeamBdg + Rand() % m_settings.deltaStartingTeamBdg;
		team->AddBudget(budget);
		m_teams.push_back(team);
	}

	m_calendar.Initialize(m_teams);
	m_classification.Initialize(m_teams);
	m_classification.GetSortedTeams(m_sortedTeams);
}

void League::Update(const bool i_doNotPrompt)
{
	m_calendar.CreateNextTurn(m_lastPlayedTurn);
	for (MatchDataVecIt It = m_lastPlayedTurn.begin(); It != m_lastPlayedTurn.end(); ++It)
	{
		MatchData& matchData = *(*It);
		Team* team0 = m_sp->GetTeamMgr().GetTeam(matchData.m_teamData[0].m_teamID);
		Team* team1 = m_sp->GetTeamMgr().GetTeam(matchData.m_teamData[1].m_teamID);
		
		const bool produceEvents = (team0->IsUserControlled() || team1->IsUserControlled());
		
		MatchInterface& match = m_sp->GetMatchMgr().GetMatchInterface(matchData);
		match.InitMatch(team0, team1, 0);
		match.PlayMatch(produceEvents);
		
		matchData = match.GetMatchData();

		if(!i_doNotPrompt && produceEvents)
		{
			//TODO: remove print stuff from GameLib
			bool isPagelle = false;
			static STL::string s_pagelle("<->");
			STD::cout << STD::endl << team0->GetName() << (team0->IsUserControlled() ? " (*)" : "" ) << " - " << team1->GetName() << (team1->IsUserControlled() ? " (*)"  : "") << "\n";
			while(getchar() != '\n');
			for (EventCollection::iterator ev = matchData.m_events.begin(); ev != matchData.m_events.end(); ++ev)
			{
				STD::cout << (*ev).minute << " ";
				if((*ev).message == s_pagelle)
					isPagelle = !isPagelle;
				else
					STD::cout << (*ev).message;

				if(!isPagelle)
					while(getchar() != '\n');
				else
					STD::cout << STD::endl;
			}
			STD::cout << STD::endl;
		}

		// update team's budget
		const int pos_sum = GetTeamPosition(team0) + GetTeamPosition(team1);
		const float pos_factor = 1.0f - (static_cast<float>( pos_sum - 1 ) / (2 * m_settings.numOfTeams - 3));
		int money = m_settings.minMatchBoxOffice + static_cast<int>(static_cast<float>(m_settings.deltaMatchBoxOffice)*pos_factor);
		money = static_cast<int>(static_cast<float>(money) * RandFloatBetween(0.9f, 1.1f));

		//TODO: move all history updates from OldMatch to Team, so all MatchInterface will only play the match.
		//      while the stuff (goal, cards, injuries) is integrated from outside
		//team0->PostMatchUpdate(matchData.m_teamData[0]);
		//team1->PostMatchUpdate(matchData.m_teamData[1]);
		
		team0->AddBudget(money);

		if(!i_doNotPrompt && produceEvents)
		{
			STD::cout << " $ " << team0->GetName() << ": guadagna " << money << " BANANE al botteghino." << STD::endl << STD::endl;
		}

		team0->EndTurnUpdate();
		team1->EndTurnUpdate();
	}
	
	// update the classification
	m_classification.Update(m_lastPlayedTurn);
	m_classification.GetSortedTeams(m_sortedTeams);
}

void League::Restart()
{
	m_calendar.Initialize(m_teams);
	m_classification.Initialize(m_teams);
	m_classification.GetSortedTeams(m_sortedTeams);
}

bool League::IsEnded() const
{
	unsigned int totTurn = (m_teams.size() - 1) * 2;
	return m_calendar.GetTurnNumber() == totTurn;
}

void League::SortTeams()
{
	m_teams = m_sortedTeams;
}

int League::GetTeamPosition(Team const * const i_team)
{
	assert(i_team);

	int index = -1;
	
	for(size_t i = 0; i < m_sortedTeams.size(); ++i)
	{
		if(i_team == m_sortedTeams[i])
		{
			index = i;
			break;
		}
	}

	return index;
}

void League::RemoveBankruptTeam(Team* const io_bankruptTeam)
{
	Utilities::RemoveAll(m_teams, io_bankruptTeam);
}

ZStream& League::Serialize(ZStream& o_stream) const
{
	o_stream.WriteOpenTag("League");

	o_stream.WriteAttribute("TeamsNum", m_teams.size());

	TeamVecCit it = m_teams.begin();
	TeamVecCit end = m_teams.end();

	for(; it != end; ++it)
	{
		o_stream.WriteAttribute("TeamID", (*it)->GetID());
	}

	m_classification.Serialize(o_stream);
	m_calendar.Serialize(o_stream);
	m_history.Serialize(o_stream);

	o_stream.WriteCloseTag("League");

	return o_stream;
}

ZStream& League::Deserialize(ZStream& i_stream)
{
	i_stream.SkipLine();

	size_t teamsNum = 0; 

	i_stream.ReadAttribute(teamsNum);

	for(size_t i = 0; i < teamsNum; ++i)
	{
		int teamID = 0;

		i_stream.ReadAttribute(teamID);

		m_teams[i] = m_sp->GetTeamMgr().GetTeam(teamID);
	}

	m_classification.Initialize(m_teams);
	m_calendar.Initialize(m_teams);

	m_classification.Deserialize(i_stream);
	m_calendar.Deserialize(i_stream);
	m_history.Deserialize(i_stream);

	i_stream.SkipLine();

	m_classification.GetSortedTeams(m_sortedTeams);

	return i_stream;
}

bool League::RemoveFirstOrLastTeams(TeamVec &o_removed, size_t i_numToRemove, bool i_removeFromEnd)
{
	for (size_t i = 0; i < i_numToRemove; ++i)
	{
		if (i_removeFromEnd)
		{
			o_removed.push_back(m_teams.back());
			m_teams.pop_back();
		}
		else
		{
			o_removed.push_back(m_teams.front());
			m_teams.erase(m_teams.begin());
		}
	}
	return true;
}

void League::EndSeasonUpdate(const bool i_bDoNotPrompt, STD::stringstream& o_message)
{
	m_history.AddLeagueItemHistory(LeagueItemHistory(m_classification.GetTeamScores(), m_sp->GetLeagueMgr().GetCurrentSeason()));

	size_t counter = 1;
	for(TeamVecIt it = m_sortedTeams.begin(); it < m_sortedTeams.end(); ++it, ++counter)
	{
		Team& theTeam = *(*it);
		const float pos_factor = static_cast<float>(m_settings.numOfTeams - counter) / static_cast<float>(m_settings.numOfTeams - 1);
		const int money = m_settings.minEndSeasonTeamBdg + static_cast<int>(pos_factor * m_settings.deltaEndSeasonTeamBdg);

		//update budget
		theTeam.AddBudget(money);
		if(!i_bDoNotPrompt)
			o_message << theTeam.GetName() << " ha guadagnato " << money << " banane!" << STD::endl;
		
		//update history
		theTeam.GetTeamHistory().AddTeamItemHistory(theTeam.GetPlayersList(), m_sp->GetLeagueMgr().GetCurrentSeason(), m_id, counter, GetTeams().size()-1);
		theTeam.GetTeamHistory().SetYearsInLeagueLayer(m_settings.layerIndex);
		if(m_settings.layerIndex == 0 && counter <= 3)
		{
			theTeam.GetTeamHistory().SetPodium(counter-1);
			theTeam.GetTeamHistory().SetTotalPodium();
		}
	}
	
	//update team points
	const TeamScoreCollection& teamScores = m_classification.GetTeamScores();
	for(TeamScoreCollection::const_iterator it= teamScores.begin(); it!= teamScores.end(); ++it)
		m_sp->GetTeamMgr().GetTeam((it->m_teamID))->AddTeamPoints(it->m_score / (GetLayerIndex()+1));
}

